package com.user_hosted_storage.server.bl;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

import javax.annotation.Resource;
import javax.annotation.security.DeclareRoles;
import javax.ejb.EJB;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response.Status;

import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.XMPPError;

import com.google.gson.Gson;
import com.user_hosted_storage.dto.ClientDTO;
import com.user_hosted_storage.dto.CommunityDTO;
import com.user_hosted_storage.dto.CommunityMemberDTO;
import com.user_hosted_storage.dto.CommunityMemberDTO.State;
import com.user_hosted_storage.dto.GroupDTO.SecurityRole;
import com.user_hosted_storage.entities.Client;
import com.user_hosted_storage.entities.Community;
import com.user_hosted_storage.entities.CommunityMember;
import com.user_hosted_storage.entities.Group;
import com.user_hosted_storage.server.dal.ClientFacade;
import com.user_hosted_storage.server.dal.CommunityFacade;
import com.user_hosted_storage.server.dal.CommunityMemberFacade;
import com.user_hosted_storage.server.dal.GroupFacade;
import com.user_hosted_storage.server.dto.DTOConverter;
import com.user_hosted_storage.server.exceptions.UnauthorizedException;
import com.user_hosted_storage.server.exceptions.UserAlreadyRegisteredException;
import com.user_hosted_storage.server.utils.PropertiesParserSingleton;
import com.user_hosted_storage.server.utils.PropertyException;
import com.user_hosted_storage.server.utils.XMPPUtils;

/**
 * Session Bean implementation class HostManager
 */
@Stateless
@DeclareRoles({"CLIENT","ADMIN"})
public class CustomerManager implements CustomersManagerRemote{

	private static final String CUSTOMER_ID = "CUSTOMER_ID";

	@EJB
	private ClientFacade clientFacade;
	
	@EJB
	private GroupFacade groupFacade; 
	
	@EJB
	private CommunityFacade communityFacade;
	
	@EJB
	private CommunityMemberFacade communityMemberFacade;
	
	@Resource
	private SessionContext sessionContext;

	private ServerProperties serverProperties;

		
    /**
     * Default constructor. 
     */
    public CustomerManager() {
        try {
			serverProperties = PropertiesParserSingleton.get(ServerProperties.class);
		} catch (PropertyException e) {
			e.printStackTrace();
		}
    }
    
    @Override
    public ClientDTO registerClient(String userName, String passwordHash, String email, long sharedSizeGB, long maximumBitrate) {
    	Client client = clientFacade.getByUserNameOrEmail(userName,email);
    	if(client!=null) {
    		throw new UserAlreadyRegisteredException();
    	}
    	
    	client = new Client();
    	client.setUserName(userName);
    	client.setPasswordHash(passwordHash);
    	client.setIp("");
    	client.setEmail(email);
    	
//    	try {
//			generateCertificate(client);
//		} catch (IOException e) {
//			e.printStackTrace();
//			throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
//		}
    	
    	clientFacade.create(client);
    	
    	Group group = new Group();
    	group.setGroupName(SecurityRole.CLIENT);
    	group.setUserName(userName);
    	
    	groupFacade.create(group);
    	
    	ClientDTO clientDTO = DTOConverter.convertToDTO(client, ClientDTO.class);
    	registerJabber(client.getId());
		return clientDTO;
    }

//    private void generateCertificate(Client client) throws IOException {
//		try {
//			Process process = Runtime.getRuntime().exec(serverProperties.KEYTOOL_PATH+" -genkey -keyalg RSA -alias "+client.getUserName()+" -keystore "+serverProperties.TMP_DIR+client.getUserName()+".jks -storepass password -validity 360 -keysize 2048");
//			process.waitFor();
//			process = Runtime.getRuntime().exec(serverProperties.KEYTOOL_PATH+"-export -alias "+client.getUserName()+" -file "+serverProperties.TMP_DIR+client.getUserName()+".crt -keystore "+serverProperties.TMP_DIR+client.getUserName()+".jks");
//			process.waitFor();
//		} catch (InterruptedException e1) {
//			e1.printStackTrace();
//		}
//		
//		File file = new File(serverProperties.TMP_DIR+client.getUserName()+".jks");
//		
//		FileInputStream in = new FileInputStream(file);
//		byte[] bytes = new byte[(int) file.length()];
//		
//		in.read(bytes);
//		
//		client.setKeystore(bytes);
//		
//		try {
//			in.close();
//		} catch (Exception e) {
//			e.printStackTrace();
//		}
//		
//		file = new File(serverProperties.TMP_DIR+client.getUserName()+".crt");
//		
//		in = new FileInputStream(file);
//		bytes = new byte[(int) file.length()];
//		
//		in.read(bytes);
//		
//		client.setCertificate(bytes);
//		
//		try {
//			in.close();
//		} catch (Exception e) {
//			e.printStackTrace();
//		}
//	}
    
//    private void regenerateTrustStore(Client client) throws IOException {
//    	
//    	File workDir = new File(serverProperties.TMP_DIR+client.getUserName()+File.separator+System.currentTimeMillis());
//    	String truststorePath = workDir.getAbsolutePath()+File.separator+"trustSrote";
//    	Collection<CommunityMember> communities = client.getCommunitiesMember();
//    	for (CommunityMember communityMember : communities) {
//    		if(communityMember.getState() == State.INVITED) {
//    			continue;
//    		}
//			Community community = communityMember.getCommunity();
//			Collection<CommunityMember> members = community.getMembers();
//			for (CommunityMember communityMember2 : members) {
//				if(communityMember2.getId() != communityMember.getId()) {
//					
//					byte[] certificate = communityMember.getClient().getCertificate();
//					
//					String certPath = workDir.getAbsolutePath()+File.separator+communityMember2.getClient().getUserName()+".crt";
//					FileOutputStream out = new FileOutputStream(certPath);
//					out.write(certificate);
//					out.flush();
//					out.close();
//					
//					try {
//						
//						Process process = Runtime.getRuntime().exec(serverProperties.KEYTOOL_PATH + "-import -file " + certPath + " -alias " + communityMember2.getClient().getUserName() + " -keystore " + truststorePath);
//						process.waitFor();
//					} catch (InterruptedException e) {
//						e.printStackTrace();
//					}
//				}
//			}
//		}
//    	
//    	File truststore = new File(truststorePath);
//    	FileInputStream in = new FileInputStream(truststore);
//    	byte[] bytes = new byte[(int) truststore.length()];
//    	in.read(bytes);
//    	try {
//			in.close();
//		} catch (Exception e) {
//			e.printStackTrace();
//		}
//    	
//    	client.setTruststore(bytes);
//    }

	@Override
    public ClientDTO loginClient() {
    	String userName = sessionContext.getCallerPrincipal().getName();
    	
    	Client client = clientFacade.getByUserName(userName);
    	if(client == null) {
    		throw new UnauthorizedException();
    	}
    	
    	sessionContext.getContextData().put(CUSTOMER_ID, client.getId());
    	return DTOConverter.convertToDTOFull(client, ClientDTO.class);
    }
    
    @Override
    public List<CommunityMemberDTO> placeFile( double fileSize, long communityId) {
    	
    	Client client = getClient();
    	
    	List<CommunityMemberDTO> result = new LinkedList<CommunityMemberDTO>();
    	
    	List<CommunityMember> hosts = communityFacade.getLeastOccupiedHosts(fileSize,communityId, client);
    	
    	for (CommunityMember host : hosts) {
    		host.increaseUsedSpace(fileSize);
		}
    	
    	for (CommunityMember host : hosts) {
			result.add(DTOConverter.convertToDTO(host, CommunityMemberDTO.class));
		}
    	
    	return result;    	
    }
    
    @Override
    public CommunityDTO createCommunity(String name){
    	
    	Client client = getClient();
    	Community community = new Community();
    	community.setName(name);
    	Collection<Client> admins = community.getAdministrators();
    	if(admins == null) {
    		admins = new LinkedList<>();
    		community.setAdministrators(admins);
    	}
    	
    	admins.add(client);
    	
    	Collection<CommunityMember> members = community.getMembers();
    	if(members == null) {
    		members= new LinkedList<>();
    		community.setMembers(members);
    	}
    	
    	communityFacade.create(community);
    	
    	CommunityMember communityMember = new CommunityMember();
		
		communityMember.setClient(client);
		communityMember.setCommunity(community);
		communityMember.setState(State.JOINED);
		communityMemberFacade.create(communityMember);
		
		members.add(communityMember);
    	
    	
    	return DTOConverter.convertToDTO(community, CommunityDTO.class);
    }

    @Override
	public void joinCommunity(long communityId, long shareSize) {
//		Community community = communityFacade.find(communityId);
		
		Client client = getClient();
		
		Collection<CommunityMember> members = client.getCommunitiesMember();
		
		
		if (members != null) {
			for (CommunityMember communityMember : members) {
				if (communityMember.getCommunity().getId() == communityId) {
					communityMember.setState(State.JOINED);
					communityMember.setSharedSize(shareSize); 
				}
			}
		}
		
//		try {
//			regenerateTrustStore(client);
//		} catch (IOException e) {
//			e.printStackTrace();
//			throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
//		}
	}

	private Client getClient() {
		String name = sessionContext.getCallerPrincipal().getName();
		Client client = clientFacade.getByUserName(name);
		if(client == null) {
			throw new WebApplicationException(Status.NOT_FOUND);
		}
		return client;
	}

	@Override
	public void updateIP(String remoteAddr) {
		Client client = getClient();
		client.setIp(remoteAddr);
	}
	
	@Override
	public void updateAcceptPort(int port) {
		getClient().setAcceptPort(port);
	}
	
	@Override
	public void updateConnectPort(int port) {
		getClient().setConnectPort(port);
	}
	
	@Override
	public ClientDTO getClient(long clientId){
		getClient();
		return DTOConverter.convertToDTO(clientFacade.find(clientId),ClientDTO.class);
	}
	
	@Override
	public String getMyCommunities(){
		
		Client client = getClient();
		Collection<CommunityMember> members = client.getCommunitiesMember();
		
		LinkedList<CommunityDTO> communities = new LinkedList<>();
		
		for (CommunityMember communityMember : members) {
			if(communityMember.getState() == State.JOINED){
				Community community = communityMember.getCommunity();
				CommunityDTO communityDTO = DTOConverter.convertToDTO(community, CommunityDTO.class);
				Collection<CommunityMember> allMembers = community.getMembers();
				for (CommunityMember member : allMembers) {
					communityDTO.addMember(DTOConverter.convertToDTO(member, CommunityMemberDTO.class));
				}
				communities.add(communityDTO);
			}
		}
		
		return new Gson().toJson(communities);
	}
	
	@Override
	public void inviteToCommunity(long communityId, long clientId){
		
		Client client = getClient();
		Client clientToInvite = clientFacade.find(clientId);
		
		Collection<CommunityMember> commMembers = clientToInvite.getCommunitiesMember();
		
		for (CommunityMember communityMember : commMembers) {
			if(communityMember.getCommunity().getId() == communityId) {
				return;
			}
		}
		
		Community community = communityFacade.find(communityId);
		
		Collection<Client> admins = community.getAdministrators();
		boolean isAdmin = false;
		for (Client admin : admins) {
			if(admin.equals(client)) {
				isAdmin = true;
			}
		}
		
		if(!isAdmin) {
			throw new WebApplicationException(Status.FORBIDDEN);
		}
		
		Collection<CommunityMember> members = community.getMembers();
		if(members == null) {
			members = new LinkedList<>();
			community.setMembers(members);
		}
		
		
		CommunityMember communityMember = new CommunityMember();
		
		communityMember.setClient(clientToInvite);
		communityMember.setCommunity(community);
		communityMember.setState(State.INVITED);
		communityMemberFacade.create(communityMember);
		
		members.add(communityMember);
		
	}
	
	@Override
	public CommunityDTO getCommunity(long communityId) {
		Community community = communityFacade.find(communityId);
		CommunityDTO communityDTO = DTOConverter.convertToDTO(community, CommunityDTO.class);
		Collection<CommunityMember> members = community.getMembers();
		
		for (CommunityMember communityMember : members) {
			communityDTO.addMember(DTOConverter.convertToDTO(communityMember, CommunityMemberDTO.class));
		}
		System.out.println(new Gson().toJson(communityDTO));
		return communityDTO;
	}
	
	@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
	@Override
	public void registerJabber(long clientId){
		Client client = clientFacade.find(clientId);
		try {
			XMPPUtils.jabberRegister(client.getUserName(), client.getPasswordHash());
			client.setRegisteredJabber(true);
		} catch (XMPPException e) {
			XMPPError xmppError = e.getXMPPError(); 
			if(xmppError.getCode() == 409){
				client.setRegisteredJabber(true);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
}
